#ifndef __H_SERVER__
#define __H_SERVER__
#include "db.hpp"
#include "match.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "util.hpp"
#define WWWROOT "./wwwroot"
#define SESSION_TIMEOUT 30000
class gobang_server
{
    private:
        wsserver_t _server;
        std::string _web_root; //web资源根目录
        user_table _ut;
        online_user_manage _om;
        room_manager _rm;
        matcher _mc;
        session_manager _sm;
    private:
        void file_handler(wsserver_t::connection_ptr conn){
            //获取到请求uri资源路径，了解客户端请求的页面文件名称
            websocketpp::http::parser::request req = conn->get_request();
            //组合出文件的实际路径(web根目录+uri)
            std::string realpath = _web_root + req.get_uri();
            //如果请求的是个目录，增加一个后缀login.html
            if(realpath.back() == '/')
            {
                realpath += "login.html";
            }
            //设置响应状态码
            conn->set_status(websocketpp::http::status_code::ok);
            //读取文件内容
            std::string body;
            bool ret = file_util::read(realpath,body);
            if(ret == false)
            {
                //文件不存在，读取文件内容失败，返回404
                body += "<html>";
                body += "<head>";
                body += "<meta charset='UTF-8'/> ";
                body += "</head>";
                body += "<body>";
                body += "<h1> Not Found </h1>";
                body += "</body>";
                body += "</html>";
                conn->set_status(websocketpp::http::status_code::not_found);
            }
            //设置响应正文
            conn->set_body(body);
        }
        void http_response(wsserver_t::connection_ptr conn,bool result,const std::string& reason,websocketpp::http::status_code::value code)
        {
            Json::Value json_resp;
            json_resp["result"] = result;
            json_resp["reason"] = reason;
            std::string resp_body;
            json_util::Serialize(json_resp,resp_body);
            conn->set_status(code);
            conn->set_body(resp_body);
            conn->append_header("Content-Type","application/json");
        }
        void reg(wsserver_t::connection_ptr conn)
        {
            //用户注册功能请求的处理
            //1、获取到请求正文
            DLOG("注册进行中...");
            std::string reqbody = conn->get_request_body();
            //2、对正文进行json反序列化，得到用户名和密码
            Json::Value user;
            bool ret = json_util::DeSerialize(reqbody,user);
            std::string username = user["username"].asString();
            std::string passwd = user["password"].asString();
            if(ret == false)
            {
                ELOG("注册信息反序列化失败！！");
                http_response(conn,false,"请求的正文格式错误",websocketpp::http::status_code::bad_request);
                return;
            }
            //3、进行数据库的用户新增操作
            if(username.empty() || passwd.empty())
            {
                ELOG("用户名或密码为空！！");
                http_response(conn,false,"请输入用户名和密码",websocketpp::http::status_code::bad_request);
                return;
            }
            //如果成功了，则返回200
            ret = _ut.insert(user);
            if(ret == false)
            {
                ELOG("用户名被占用！！");
                http_response(conn,false,"用户名已被占用",websocketpp::http::status_code::bad_request);
                return;
            }
            
            return http_response(conn,true,"用户注册成功",websocketpp::http::status_code::ok);
        }
        void login(wsserver_t::connection_ptr conn)
        {
            //用户登录功能请求的处理
            //1、获取请求正文，并进行json反序列化，得到用户名和密码
            DLOG("登录进行中...");
            std::string reqbody = conn->get_request_body();
            Json::Value user;
            bool ret = json_util::DeSerialize(reqbody,user);
            std::string username = user["username"].asString();
            std::string passwd = user["password"].asString();
            if(ret == false)
            {
                ELOG("登录信息反序列化失败！！");
                http_response(conn,false,"请求的正文格式错误",websocketpp::http::status_code::bad_request);
                return;
            }
            //2、校验正文完整性，进行数据库的用户信息验证
            if(username.empty() || passwd.empty())
            {
                ELOG("用户名或密码为空！！");
                return http_response(conn,false,"用户名或密码为空！！",websocketpp::http::status_code::bad_request);
            }
            ret = _ut.login(user);
            if(ret == false)
            {
                ELOG("用户名或密码错误！！");
                return http_response(conn,false,"用户名或密码错误！！",websocketpp::http::status_code::bad_request);
            }
            //如果验证成功，给客户端创建session
            uint64_t uid = user["id"].asUInt64();
            session_ptr ssp = _sm.create_session(uid,LOGIN);
            if(ssp.get() == nullptr)
            {
                ELOG("会话创建失败！！");
                return http_response(conn,false,"会话创建失败！！",websocketpp::http::status_code::internal_server_error);
            }
            _sm.set_session_expire_time(ssp->ssid(),SESSION_TIMEOUT);
            //3、设置响应头部：Set-Cookie，将ssid通过cookie返回
            std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());
            conn->append_header("Set-Cookie", cookie_ssid.c_str());
            return http_response(conn,true,"登录成功！！",websocketpp::http::status_code::ok); 
        }
        bool get_cookie_val(const std::string& cookie_str, const std::string& key , std::string& val)
        {
            std::vector<std::string> cookie_arr;
            string_util::split(cookie_str,"; ",cookie_arr);
            for(auto& str : cookie_arr)
            {
                std::vector<std::string> kv;
                string_util::split(str,"=",kv);
                if(kv.size() != 2){continue;}
                if(kv[0] == key)
                {
                    val = kv[1];
                    return true;
                }
            }
            return false;
        }
        void info(wsserver_t::connection_ptr conn)
        {
            //用户信息获取功能请求的处理
            //1、获取请求信息中的cookie，从cookie中获取ssid
            std::string cookie_str = conn->get_request_header("Cookie");
            //      如果没有cookie，返回错误：没有cookie信息，让客户端重新登录
            if(cookie_str.empty()){
                ELOG("Cookie信息为空，请重新登录");
                return http_response(conn,false,"Cookie信息为空，请重新登录",websocketpp::http::status_code::bad_request);
            }
            std::string ssid_str;
            bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
            if(ret == false)
            {
                ELOG("未找到SSID，请重新登录");
                return http_response(conn,false,"未找到SSID，请重新登录",websocketpp::http::status_code::bad_request);
            }
            //2、在session管理中查找对应的会话信息
            //      没有找到session，则认为登录已经过期，需要重新登录
            session_ptr ssp = _sm.get_session(std::stol(ssid_str));
            if(ssp.get() == nullptr)
            {
                ELOG("登录已过期，请重新登录");
                return http_response(conn,false,"登录已过期，请重新登录",websocketpp::http::status_code::bad_request);
            }
            Json::Value user;
            ret = _ut.select_by_id(ssp->get_user(),user);
            if(ret == false)
            {
                ELOG("该用户不存在");
                return http_response(conn,false,"该用户不存在",websocketpp::http::status_code::bad_request);
            }
            //3、从数据库中取出用户信息，进行序列化发送给客户端
            std::string body;
            json_util::Serialize(user, body);
            conn->set_body(body);
            conn->append_header("Content-Type", "application/json");
            conn->set_status(websocketpp::http::status_code::ok);
            //4、刷新session的过期时间
            _sm.set_session_expire_time(ssp->ssid(),SESSION_TIMEOUT);
        }
        void http_callback(websocketpp::connection_hdl hdl)
        {
            std::string pathname = _web_root;
            wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            std::string method = req.get_method();
            if(method == "POST" && uri == "/reg"){
                return reg(conn);
            }else if(method == "POST" && uri == "/login"){
                return login(conn);
            }else if(method == "GET" && uri == "/info"){
                return info(conn);
            }else{
                return file_handler(conn);
            }
        }
        void open_game_hall(wsserver_t::connection_ptr conn)
        {
            //游戏大厅长连接
            //1、登录验证，判断当前客户端是否已经成功登录
            session_ptr ssp = get_session_by_cookie(conn);
            if(ssp.get() == nullptr) { return; }
            //2、判断当前客户端是否重复登录
            Json::Value err_resp;
            if(_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user()))
            {
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "用户重复登录";
                err_resp["result"] = false;
                std::string body;
                json_util::Serialize(err_resp,body);
                conn->send(body);
                return;
            }
            //3、将当前客户端以及连接加入到游戏大厅
            _om.enter_game_hall(ssp->get_user(),conn);
            Json::Value json_resp;
            //4、给客户端响应游戏大厅连接建立成功
            json_resp["optype"] = "hall_ready";
            json_resp["result"] = true;
            std::string body;
            json_util::Serialize(json_resp,body);
            conn->send(body);
            //5、将session设置为永久存在
            return _sm.set_session_expire_time(ssp->ssid(),TIMER_FOREVER);
        }
        void open_game_room(wsserver_t::connection_ptr conn)
        {
            //1、获取当前客户端的session
            session_ptr ssp = get_session_by_cookie(conn);
            Json::Value json_resp;
            if(ssp.get() == nullptr)
            {
                json_resp["optype"] = "room_ready";
                json_resp["reason"] = "用户不存在，请重新登录！";
                json_resp["result"] = false;
                std::string body;
                json_util::Serialize(json_resp,body);
                conn->send(body);
                return;
            }
            //2、当前用户是否已经在游戏房间或者游戏大厅中
            if(_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user()))
            {
                json_resp["optype"] = "room_ready";
                json_resp["reason"] = "用户重复登录！";
                json_resp["result"] = false;
                std::string body;
                json_util::Serialize(json_resp,body);
                conn->send(body);
                return;
            }
            //3、判断当前用户是否已经创建好了房间
            room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
            if(rp.get() == nullptr)
            {
                json_resp["optype"] = "room_ready";
                json_resp["reason"] = "未找到用户的房间信息";
                json_resp["result"] = false;
                std::string body;
                json_util::Serialize(json_resp,body);
                conn->send(body);
                return;
            }
            //4、将当前用户添加到在线用户管理的游戏房间中
            _om.enter_game_room(ssp->get_user(),conn);
            //5、将session重新设置为永久存在
            _sm.set_session_expire_time(ssp->ssid(),TIMER_FOREVER);
            json_resp["optype"] = "room_ready";
            json_resp["result"] = true;
            json_resp["room_id"] = static_cast<unsigned long long>(rp->id());
            json_resp["uid"] = static_cast<unsigned long long>(ssp->get_user());
            json_resp["white_id"] = static_cast<unsigned long long>(rp->get_white_id());
            json_resp["black_id"] = static_cast<unsigned long long>(rp->get_black_id());
            std::string resp_body;
            json_util::Serialize(json_resp,resp_body);
            conn->send(resp_body);
        }
        void open_callback(websocketpp::connection_hdl hdl)
        {
            //根据请求URI分类是什么请求!
            wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            if(uri == "/hall"){
                //游戏大厅长连接
                open_game_hall(conn);
            }else if(uri == "/room"){
                open_game_room(conn);
            }
        }
        session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn)
        {
            Json::Value err_resp;
            std::string cookie_str = conn->get_request_header("Cookie");
            if(cookie_str.empty())
            {
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "没有找到cookie信息，请重新登录";
                err_resp["result"] = false;
                std::string body;
                json_util::Serialize(err_resp,body);
                conn->send(body);
                return session_ptr();
            }
            std::string ssid_str;
            if(get_cookie_val(cookie_str,"SSID",ssid_str) == false)
            {
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "没有找到ssid，请重新登录";
                err_resp["result"] = false;
                std::string body;
                json_util::Serialize(err_resp,body);
                conn->send(body);
                return session_ptr();
            }
            session_ptr ssp = _sm.get_session(std::stol(ssid_str));
            if(ssp.get() == nullptr)
            {
                err_resp["optype"] = "hall_ready";
                err_resp["reason"] = "登录已过期，请重新登录";
                err_resp["result"] = false;
                std::string body;
                json_util::Serialize(err_resp,body);
                conn->send(body);
                return session_ptr();
            }
            return ssp;

        }
        void close_game_hall(wsserver_t::connection_ptr conn)
        {
            //1、将玩家从游戏大厅中移除
            Json::Value err_resp;
            session_ptr ssp = get_session_by_cookie(conn);
            if(ssp.get() == nullptr) return;
            _om.exit_game_hall(ssp->get_user());
            //2、将session恢复生命周期管理，设置定时销毁
            _sm.set_session_expire_time(ssp->ssid(),SESSION_TIMEOUT);
        }
        void close_game_room(wsserver_t::connection_ptr conn)
        {
            //获取会话信息，识别客户端
            session_ptr ssp = get_session_by_cookie(conn);
            if(ssp.get() == nullptr) return;
            //1.将玩家从在线用户管理中移除
            _om.exit_game_room(ssp->get_user());
            //2.将session恢复生命周期管理，设置定时销毁
            _sm.set_session_expire_time(ssp->get_user(),SESSION_TIMEOUT);
            //3.将玩家从游戏房间中移除，房间中所有用户退出了就会销毁房间
            _rm.remove_room_user(ssp->get_user());
        }
        void close_callback(websocketpp::connection_hdl hdl)
        {
            wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            if(uri == "/hall"){
                //游戏大厅长连接
                close_game_hall(conn);
            }else if(uri == "/room"){
                close_game_room(conn);
            }
        }
        void message_game_hall(wsserver_t::connection_ptr conn,wsserver_t::message_ptr msg)
        {
            Json::Value json_resp;
            std::string resp_body;
            //1、身份验证，当前客户端到底是哪个玩家
            session_ptr ssp = get_session_by_cookie(conn);
            if(ssp.get() == nullptr) return;
            //2、获取请求信息
            std::string req_body = msg->get_payload();
            if(req_body.empty())
            {
                ELOG("msg::get_payload error!");
                return;
            }
            Json::Value req_json;
            bool ret = json_util::DeSerialize(req_body,req_json);
            if(ret == false)
            {
                json_resp["result"] = false;
                json_resp["reason"] = "请求信息解析失败！";
                ret = json_util::Serialize(json_resp,resp_body);
                conn->send(resp_body);
                return;
            }
            std::string temp;
            json_util::Serialize(req_json,temp);
            if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
            {
                //  开始对战匹配:通过匹配模块，将用户添加到匹配队列中
                _mc.add(ssp->get_user());
                json_resp["optype"] = "match_start";
                json_resp["result"] = true;
                json_util::Serialize(json_resp,resp_body);
                conn->send(resp_body);
                return;
            }else if(!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
            {
                //  停止对战匹配:通过匹配模块，将用户从匹配队列中移除
                _mc.del(ssp->get_user());
                json_resp["optype"] = "match_stop";
                json_resp["result"] = true;
                json_util::Serialize(json_resp,resp_body);
                conn->send(resp_body);
                return;
            }
            json_resp["optype"] = "Unknow";
            json_resp["result"] = false;
            json_util::Serialize(json_resp,resp_body);
            conn->send(resp_body);
        }
        void message_game_room(wsserver_t::connection_ptr conn,wsserver_t::message_ptr msg)
        {
            //1、获取客户端session，识别客户端身份
            Json::Value json_resp;
            session_ptr ssp = get_session_by_cookie(conn);
            if(ssp.get() == nullptr) return;
            //2、获取客户端房间信息
            room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
            if(rp.get() == nullptr)
            {
                json_resp["optype"] = "Unknow";
                json_resp["reason"] = "未找到用户的房间信息";
                json_resp["result"] = false;
                std::string body;
                json_util::Serialize(json_resp,body);
                conn->send(body);
                return;
            }
            //3、对消息进行反序列化
            std::string resp_body = msg->get_payload();
            bool ret = json_util::DeSerialize(resp_body,json_resp);
            if(ret == false)
            {
                json_resp["optype"] = "Unknow";
                json_resp["reason"] = "请求解析失败！";
                json_resp["result"] = false;
                std::string body;
                json_util::Serialize(json_resp,body);
                conn->send(body);
                return;
            }
            //4、通过房间模块进行消息请求处理
            rp->handle_request(json_resp);
        }
        void message_callback(websocketpp::connection_hdl hdl,
                    wsserver_t::message_ptr msg)
        {
            wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            if(uri == "/hall"){
                //游戏大厅消息处理
                message_game_hall(conn,msg);
            }else if(uri == "/room"){
                message_game_room(conn,msg);
            }
        }
    public:
        gobang_server(
            const std::string &host,
            const std::string &user,
            const std::string &passwd,
            uint32_t port,
            const std::string &dbname,
            const std::string &wwwroot = WWWROOT)
        :_ut(host,user,passwd,port,dbname),
        _web_root(wwwroot),
        _om(),
        _rm(&_ut,&_om),
        _mc(&_rm,&_om,&_ut),
        _sm(&_server)
        {
            _server.set_access_channels(websocketpp::log::alevel::none);
            //3、初始化asio框架
            _server.init_asio();
            _server.set_reuse_addr(true);
            //4、设置业务处理回调函数
            _server.set_close_handler(std::bind(&gobang_server::close_callback,this,std::placeholders::_1));
            _server.set_open_handler(std::bind(&gobang_server::open_callback,this,std::placeholders::_1));
            _server.set_http_handler(std::bind(&gobang_server::http_callback,this,std::placeholders::_1));
            _server.set_message_handler(std::bind(&gobang_server::message_callback,this,std::placeholders::_1,std::placeholders::_2));
        }
        void start(int port)
        {
            _server.listen(port);
            _server.start_accept();
            _server.run();
        }
};

#endif